//
//  TrackDatabase.m
//  MantuPlay
//
//  Created by Markus Sintonen on 22.3.2009.
//  Copyright 2009 __MyCompanyName__. All rights reserved.
//

#import "TrackDatabase.h"
#import "SettingsDatabase.h"
#import "ID3TagReader.h"
#import "Track.h"
#import "Album.h"
#import "Artist.h"
#import "Genre.h"
#import "TrackFilepath.h"
#import "DBIndex.h"
#import "FMDatabaseAdditions.h"
#import "NSStringExtensions.h"
#import "NSArrayExtensions.h"
#import "UIImageExtensions.h"
#import "DebuggingMacros.h"
#import <dirent.h>
#import <sys/types.h>
#import <sys/stat.h>
#import <sys/syslimits.h>

@implementation TrackDatabase

SINGLETON_IMPLEMENTATION(TrackDatabase, sharedTrackDatabase)

-(id)init {
	if ((self = [super init]) != nil) {
		[self setupTrackDatabase];
	}
	return self;
}

- (void)setupTrackDatabase {
	SettingsDatabase *settings = [SettingsDatabase sharedSettingsDatabase];
	NSString *databasePath = [[settings applicationSettingsFolder] stringByAppendingPathComponent:@"musicdb.sqldb"];
	
	NSFileManager *fileManager = [NSFileManager defaultManager];
	
	//[fileManager removeItemAtPath:databasePath error:nil]; //REMOVE, THIS IS JUST FOR DEBUGGING THE DATABASE CREATION
	
	BOOL databaseFileExisted = YES;
	if(![fileManager fileExistsAtPath:databasePath]) {
		databaseFileExisted = NO;
		[fileManager createFileAtPath:databasePath contents:[NSData data] attributes:nil]; //File didnt exist, create it
	}
	
	db = [[FMDatabase alloc] initWithPath:databasePath];
	assert([db open]);
	
	if(!databaseFileExisted) {
		[db executeUpdate:@"CREATE TABLE trackdb (rowid INTEGER PRIMARY KEY, title TEXT, album TEXT, artist TEXT, genre TEXT, track_number NUMERIC, part_of_set NUMERIC, scan_dir_id NUMERIC, file_path TEXT)"];
		
		[db setShouldCacheStatements:YES];
		
		START_TIMER;
		
		[db beginTransaction];
		for(unsigned int ind = 0; ind < [[settings trackScanningDirectories] count]; ind++) {
			[self scanTracksFromDirectory:"" relativeToTrackScanningDirectoryID:ind];
		}
		[db commit];
		
		[db clearCachedStatements];
		
		END_TIMER(@"Scanning tracks");
	}
}

-(void)scanTracksFromDirectory:(std::string)directory relativeToTrackScanningDirectoryID:(unsigned int)scanDirectoryID {	
	DIR *pathHandle;
	struct dirent *entry;
	
	SettingsDatabase *settings = [SettingsDatabase sharedSettingsDatabase];
	std::string scanDirectory = std::string([[[settings trackScanningDirectories] objectAtIndex:scanDirectoryID] UTF8String]);
	std::string currentFilePathRelativeToScanDir;
	std::string currentFullFilePath;
	std::string currentDirectory;
	
	currentDirectory = scanDirectory + directory;
	
	pathHandle = opendir(currentDirectory.c_str());
	
	while(1) {
   		entry = readdir(pathHandle);
		
    	if (entry == NULL) break;
		
		if(entry->d_name[0] == '.') continue;
		
		currentFilePathRelativeToScanDir = directory + "/" + std::string(entry->d_name);
		currentFullFilePath = scanDirectory + currentFilePathRelativeToScanDir;
		
	    if (entry->d_type == DT_DIR) {
			[self scanTracksFromDirectory:currentFilePathRelativeToScanDir relativeToTrackScanningDirectoryID:scanDirectoryID];
   		}
    	else if (entry->d_type == DT_REG) {
			if(isMP3FileExtension(entry->d_name)) {
				NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
				ID3TagReader *tag = [[ID3TagReader alloc] initWithFile:currentFullFilePath.c_str() framesToRead:textFrameTypes];
				
				if([tag.title length] <= 0)
					tag.title = [[NSString stringWithUTF8String:entry->d_name] stringByDeletingPathExtension];
				
				[db executeUpdate:@"INSERT INTO trackdb (title, album, artist, genre, track_number, part_of_set, scan_dir_id, file_path) values (?, ?, ?, ?, ?, ?, ?, ?)",
														 tag.title,
														 tag.album,
														 tag.artist,
														 tag.genre,
														 tag.trackNumber,
														 tag.partOfSet,
														 [DBIndex indexWithIndex:scanDirectoryID],
														 [NSString stringWithUTF8String:currentFilePathRelativeToScanDir.c_str()]];
				
				[tag release];
				[pool release];
			}
    	}
  	}
	closedir(pathHandle);
}

-(NSArray*)tracksFromAlbum:(NSString*)album artist:(NSString*)artist genre:(NSString*)genre orderByTrackNumber:(BOOL)orderByTrackNum {
	NSMutableString *sqlQuery = [[NSMutableString alloc] initWithString:@"SELECT * FROM trackdb"];
	[sqlQuery appendString:[self constructFilterSqlQueryWithAlbum:album artist:artist genre:genre]];
	
	if(orderByTrackNum) [sqlQuery appendString:@" ORDER BY part_of_set, track_number"];
	else [sqlQuery appendString:@" ORDER BY title"];
	
	FMResultSet *rs = [db executeQuery:sqlQuery withArgumentsInArray:[self constructSqlQueryArgumentsWithAlbum:album artist:artist genre:genre]];
	[sqlQuery release];
	NSArray *tracks = [self databaseObjectArrayWithClasses:[Track class] fromResultSet:rs];
	[rs close];
	return tracks;
}

-(NSArray*)albumsFromArtist:(NSString*)artist genre:(NSString*)genre {
	NSMutableString *sqlQuery = [[NSMutableString alloc] initWithString:@"SELECT rowid, album, artist, part_of_set FROM trackdb"];
	[sqlQuery appendString:[self constructFilterSqlQueryWithAlbum:nil artist:artist genre:genre]];
	[sqlQuery appendString:@" GROUP BY album"];
		
	FMResultSet *rs = [db executeQuery:sqlQuery withArgumentsInArray:[self constructSqlQueryArgumentsWithAlbum:nil artist:artist genre:genre]];
	[sqlQuery release];
	NSArray *tracks = [self databaseObjectArrayWithClasses:[Album class] fromResultSet:rs];
	[rs close];
	return tracks;
}

-(NSArray*)artistsFromGenre:(NSString*)genre {
	NSMutableString *sqlQuery = [[NSMutableString alloc] initWithString:@"SELECT rowid, artist FROM trackdb"];
	[sqlQuery appendString:[self constructFilterSqlQueryWithAlbum:nil artist:nil genre:genre]];
	[sqlQuery appendString:@" GROUP BY artist"];
	
	FMResultSet *rs = [db executeQuery:sqlQuery withArgumentsInArray:[self constructSqlQueryArgumentsWithAlbum:nil artist:nil genre:genre]];
	[sqlQuery release];
	NSArray *tracks = [self databaseObjectArrayWithClasses:[Artist class] fromResultSet:rs];
	[rs close];
	return tracks;
}

-(NSArray*)allGenres {
	FMResultSet *rs = [db executeQuery:@"SELECT rowid, genre FROM trackdb GROUP BY genre" withArgumentsInArray:[NSArray array]];
	NSArray *genres = [self databaseObjectArrayWithClasses:[Genre class] fromResultSet:rs];
	[rs close];
	return genres;
}

-(NSArray*)databaseObjectArrayWithClasses:(Class)dbClass fromResultSet:(FMResultSet*)rs {
	NSMutableArray *objects = [[NSMutableArray allocWithZone:nil] init];
	while([rs next]) {
		id obj = [[dbClass allocWithZone:nil] init];
		((GenericDatabaseObject*)obj).ID = [DBIndex indexWithIndex:(unsigned int)[rs longForColumnIndex:0]];
		((GenericDatabaseObject*)obj).title = [rs stringForColumnIndex:1];
		
		if([obj isKindOfClass:[Track class]]) {
			Track *track = (Track*)obj;
			track.album = [rs stringForColumnIndex:2];
			track.artist = [rs stringForColumnIndex:3];
			track.genre = [rs stringForColumnIndex:4];
			track.trackNumber = [NSNumber numberWithInt:[rs intForColumnIndex:5]];
			track.partOfSet = [NSNumber numberWithInt:[rs intForColumnIndex:6]];
			track.trackFilepath = [TrackFilepath trackFilepathWithTrackScanDirectoryID:[rs longForColumnIndex:7] filepathRelativeToScanDir:[rs stringForColumnIndex:8]];
		}
		else if([obj isKindOfClass:[Album class]]) {
			Album *album = (Album*)obj;
			album.albumArtist = [rs stringForColumnIndex:2];
			if([rs intForColumnIndex:3]) album.isCompilation = YES;
		}
		if(((GenericDatabaseObject*)obj).title) [objects addObject:obj];
		[obj release];
	}
	return (NSArray*)[objects autorelease];
}

-(NSString*)getFilepathForFirstTrackInAlbum:(NSString*)album {
	SettingsDatabase *settings = [SettingsDatabase sharedSettingsDatabase];
	FMResultSet *rs = [db executeQuery:@"SELECT scan_dir_id, file_path FROM trackdb WHERE album = ?", album];
	[rs next];
	NSString *trackPath = [[[settings trackScanningDirectories] objectAtIndex:[rs intForColumnIndex:0]] stringByAppendingString:[rs stringForColumnIndex:1]];
	[rs close];
	return trackPath;
}

-(NSString*)constructFilterSqlQueryWithAlbum:(NSString*)album artist:(NSString*)artist genre:(NSString*)genre {
	NSMutableString *sqlQuery = [[NSMutableString alloc] init];
	NSString *whereAnd = @"WHERE";
	
	if(album) {
		[sqlQuery appendFormat:@" %@ album = ?", whereAnd];
		whereAnd = @"AND";
	}
	if(artist) {
		[sqlQuery appendFormat:@" %@ artist = ?", whereAnd];
		whereAnd = @"AND";
	}
	if(genre) {
		[sqlQuery appendFormat:@" %@ genre = ?", whereAnd];
	}
	return [sqlQuery autorelease];
}
-(NSArray*)constructSqlQueryArgumentsWithAlbum:(NSString*)album artist:(NSString*)artist genre:(NSString*)genre {
	NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:3];
	if(album) [array addObject:album];
	if(artist) [array addObject:artist];
	if(genre) [array addObject:genre];
	return [array autorelease];
}

-(void)clearDatabaseCachedStatements {
	[db clearCachedStatements];
}

BOOL isMP3FileExtension(const char *filename) {
	char *found = strrchr(filename, '.');
	if(found == NULL) return FALSE;
	
	if(strcmp(found + 1, "mp3") == 0) return TRUE;
	else return FALSE;
}

- (void)dealloc {
	[db release];
	[super dealloc];
}
@end

@implementation AlbumArtLoadOperation
-(id)initWithImageView:(id)imView albumName:(NSString*)albumName imageSize:(CGSize)imageSize {
	if (![super init]) return nil;
	
	imageView = imView;
	album = [[NSString alloc] initWithString:albumName];
	size = imageSize;
	
	return self;
}

-(void)main {
	if([self isCancelled]) return;
	
	SettingsDatabase *settings = [SettingsDatabase sharedSettingsDatabase];
	//NSString *applicationSettingsFolder = [[[NSString alloc] initWithString:[[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask,YES) objectAtIndex:0] stringByAppendingPathComponent:@"MantuPlay"]] autorelease];
	//NSString *albumArtFolder = [[[NSString alloc] initWithString:[applicationSettingsFolder stringByAppendingPathComponent:@"AlbumArt"]] autorelease];
	NSString *thumbPath = [NSString stringWithFormat:@"%@/%.0fx%.0f_%@.jpg", settings.albumArtFolder, size.width, size.height, album];
	UIImage *thumb = nil;
	NSFileManager *fileManager = [NSFileManager defaultManager];
	if(![fileManager fileExistsAtPath:thumbPath]) {
		NSString *trackPath = [[TrackDatabase sharedTrackDatabase] getFilepathForFirstTrackInAlbum:album];
		ID3TagReader *tag = [[ID3TagReader alloc] initWithFile:[trackPath UTF8String] framesToRead:AlbumArtType];
		
		if([self isCancelled]) {[tag release]; return;}
		if([tag.albumArtData length] > 0) {
			thumb = [[UIImage imageWithData:tag.albumArtData] scaleImageToSize:size keepAspectRatio:YES];
			[thumb writeJPEGImageToFile:thumbPath compressionQuality:1.0];
		} else {
			[fileManager createFileAtPath:thumbPath contents:[NSData data] attributes:nil];
		}
		[tag release];
	} else {
		thumb = [UIImage imageWithContentsOfFile:thumbPath];
	}
	if(!thumb) thumb = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"DefaultAlbumArtSmall" ofType:@"png"]];
	
	if([self isCancelled]) return;
	
	if((imageView != nil) && ([imageView retainCount] > 0))
		[imageView performSelectorOnMainThread:@selector(setImage:) withObject:thumb waitUntilDone:YES];
}

- (void)dealloc {
    [album release];
	album = nil;
    [super dealloc];
}
@end
